package netty.tcp;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.timeout.IdleStateHandler;
import netty.common.serializer.TcpDecoder;
import netty.common.serializer.TcpEncoder;
import netty.tcp.reveive.ServerHandler;
import netty.tcp.websocket.ChannelEventHandler;
import netty.tcp.websocket.MessageInHandler;
import netty.tcp.websocket.MessageOutHandler;

public class TCPServer {

    private final int receivePort = 9955;
    private final int webSocketPort = 8765;

    public void start() throws Exception {
        startReceiveServer();
        startWebSocketServer();
    }

    public void startReceiveServer() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // (3)
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .childOption(ChannelOption.SO_SNDBUF, 6000*1024)
                    .option(ChannelOption.SO_RCVBUF, 6000*1024)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 15000)
                    .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(512 * 1024, 1024 * 1024))
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
//                            ch.pipeline().addLast(new JdkZlibEncoder());
//                            ch.pipeline().addLast(new JdkZlibDecoder());
                            ch.pipeline().addLast(new TcpEncoder());
                            ch.pipeline().addLast(new TcpDecoder());
                            ch.pipeline().addLast(new ServerHandler());
                        }
                    });


            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(receivePort).sync(); // (7)

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture();
        } finally {
//            workerGroup.shutdownGracefully();
//            bossGroup.shutdownGracefully();
        }
    }

    public void startWebSocketServer() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {

            ServerBootstrap b = new ServerBootstrap();
            //childOption是在客户端连接connect之后处理的handler，不带child的是在客户端初始化时需要进行处理的
            //设置时间循环对象，前者用来处理accept事件，后者用于处理已经建立的连接的io
            //Server是NioServerSocketChannel 客户端是NioSocketChannel绑定了jdk NIO创建的ServerSocketChannel对象,
            b.group(bossGroup, workerGroup)
                    //用它来建立新accept的连接
                    .channel(NioServerSocketChannel.class)
                    // 第2次握手服务端向客户端发送请求确认，同时把此连接放入队列A中，
                    // 然后客户端接受到服务端返回的请求后，再次向服务端发送请求，表示准备完毕，此时服务端收到请求，把这个连接从队列A移动到队列B中，
                    // 此时A+B的总数，不能超过SO_BACKLOG的数值，满了之后无法建立新的TCP连接,2次握手后和3次握手后的总数
                    // 当服务端从队列B中按照FIFO的原则获取到连接并且建立连接[ServerSocket.accept()]后，B中对应的连接会被移除，这样A+B的数值就会变小
                    //此参数对于程序的连接数没影响，会影响正在准备建立连接的握手。
                    .option(ChannelOption.SO_BACKLOG, 1024)//
                    //启用心跳，双方TCP套接字建立连接后（即都进入ESTABLISHED状态），
                    // 并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活，TCP会自动发送一个活动探测数据报文
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //TCP协议中，TCP总是希望每次发送的数据足够大，避免网络中充满了小数据块。
                    // Nagle算法就是为了尽可能的发送大数据快。
                    // TCP_NODELAY就是控制是否启用Nagle算法。
                    // 如果要求高实时性，有数据发送时就马上发送，就将该选项设置为true关闭Nagle算法；
                    // 如果要减少发送次数减少网络交互，就设置为false等累积一定大小后再发送。默认为false。
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_SNDBUF, 1024 * 512)
                    .childOption(ChannelOption.SO_RCVBUF, 1024 * 512)
                    //是否允许重复绑定端口，重复启动，会把端口从上一个使用者上抢过来
                    .option(ChannelOption.SO_REUSEADDR, true)
                    //连接超时毫秒
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)//缓冲池
                    .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(1024 * 512, 1024 * 1024))
                    //超高水位,影响channel.isWritable方法,如果达到超高水位,将不再缓冲区写入数据
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    //接收http get请求
                                    .addLast(new HttpServerCodec())
                                    //接收http post请求,未来做ws代理http请求,再开放
                                    .addLast(new HttpObjectAggregator(1024))
                                    //心跳检查机制,如果超过多久没有发生对应事件,将触发userEventTrigger,设置为0将代表不检测,此处只做读事件检测
                                    .addLast(new IdleStateHandler(0, 0, 0))
                                    //压缩
                                    .addLast(new WebSocketServerCompressionHandler())
                                    //服务端返回消息给客户端
                                    .addLast(new ChannelEventHandler())
                                    .addLast(new MessageOutHandler())
                                    .addLast(new MessageInHandler());
                        }
                    });

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(webSocketPort).sync(); // (7)

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture();
        } finally {
//            workerGroup.shutdownGracefully();
//            bossGroup.shutdownGracefully();
        }
    }

}
